package grame;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.KeyEventDispatcher;
import java.awt.KeyboardFocusManager;
import java.awt.Toolkit;
import java.awt.event.KeyEvent;
import javax.swing.JFrame;
import javax.swing.UIManager;
/**
 * The base for making games. This is the grid.
 * @author Moomoohk (moomoohk@ymail.com)
 */
public class Base
{
	protected JFrame gridFrame;
	protected Panel gridPanel;
	protected KeyEventDispatcher d;
	protected Coordinates center;
	protected boolean [][] occupied, checked;
	protected Coin [][] coins;
	protected Entity [][] pos;
	protected Entity player;
	protected boolean edges;
	protected final Color DEFAULT_COLOR=Color.white;
	/**
	 * Generates a new grid
	 * @param row Number of rows	
	 * @param col Number of columns
	 * @param squareSize Size of squares (30 is good)
	 * @param title Title of window
	 */
	public Base(int row, int col, int squareSize, String title)
	{
		this(row, col, squareSize, title, false);
	}
	/**
	 * Generates a new grid
	 * @param row Number of rows	
	 * @param col Number of columns
	 * @param squareSize Size of squares (30 is good)
	 * @param title Title of window
	 * @param edges Disables obstacle loading on the border (recommended true)
	 */
	public Base(int row, int col, int squareSize, String title, boolean edges)
	{
		try
		{
			UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
		}
		catch (Exception exception1)
		{
			System.err.println("Error loading L&F: " + exception1);
		}
		this.center=new Coordinates(row/2, col/2);
		this.gridFrame = new JFrame();
		setTitle(title);
		if (squareSize < 6)
			squareSize = 6;
		this.gridPanel = new Panel(row, col, squareSize, squareSize);
		this.gridPanel.setDefaultColor(Color.BLACK);
		this.gridPanel.setGroutingColor(Color.BLACK);
		this.gridFrame.getContentPane().add(this.gridPanel);
		Dimension screensize = Toolkit.getDefaultToolkit().getScreenSize();
		this.gridFrame.setLocation((screensize.width - this.gridFrame.getWidth()) / 2, (screensize.height - this.gridFrame.getHeight()) / 2);
		this.gridFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		clear();
		this.occupied=new boolean[row][col];
		this.pos=new Entity[row][col];
		this.coins=new Coin[row][col];
		for(int i=0; i<row; i++)
			for(int j=0; j<col; j++)
			{
				this.occupied[i][j]=false;
				this.pos[i][j]=null;
				this.coins[i][j]=null;
			}
		this.player=null;
		this.d=new KeyEventDispatcher()
		{ 
			public boolean dispatchKeyEvent(KeyEvent e)
			{
				if(e.getID() == KeyEvent.KEY_PRESSED)
				{
					KeyboardFocusManager.getCurrentKeyboardFocusManager().removeKeyEventDispatcher(this);
					handleKeyPress(e.getKeyCode());
				}
				return false;
			}
		};
		this.edges=edges;
		this.gridFrame.pack();
		this.setVisible(true);
	}
	/**
	 * Disables/enables obstacles loading on the borders
	 * @param edges True to enable borders, false to disable
	 */
	public void setEdges(boolean edges)
	{
		this.edges=edges;
	}
	private void clear(Color color)
	{
		for (int i = 0; i < getRows(); i++)
			for (int j = 0; j < getColumns(); j++)
			{
				this.gridPanel.setColor(new Coordinates(j, i), color);
				this.gridPanel.setSquareText(new Coordinates(j, i), "");
			}
	}
	/**
	 * Colors all the squares white
	 */
	public void clear()
	{
		clear(Color.WHITE);
	}
	/**
	 * @return Number of rows in the grid
	 */
	public int getRows()
	{
		return this.gridPanel.getRowCount();
	}
	/**
	 * @return Number of columns in the grid
	 */
	public int getColumns()
	{
		return this.gridPanel.getColumnCount();
	}
	/**
	 * Basically Thread.sleep
	 * @param mils Amount of time (in milliseconds) to sleep
	 */
	public void delay(long mils)
	{
		try
		{
			Thread.sleep(mils);
		} 
		catch (InterruptedException e) 
		{
			e.printStackTrace();
		}
	}
	/**
	 * Gets rid of the base
	 */
	public void close()
	{
		this.gridFrame.setVisible(false);
		this.gridFrame.dispose();
	} 
	/**
	 * Checks whether this base is visible or not
	 * @return True if visible, else false
	 */
	public boolean isOpen()
	{
		return this.gridFrame.isVisible();
	}
	/**
	 * Set visibility of base
	 * @param status True to make visible, false to hide
	 */
	public void setVisible(boolean status)
	{
		this.gridFrame.setVisible(status);
	}
	/**
	 * Set title of base
	 * @param title String to use in title. Leave blank for "Moshe's Game"
	 */
	public void setTitle(String title)
	{
		if ((title == null) || (title.equals("")))
			this.gridFrame.setTitle("Base");
		else
			this.gridFrame.setTitle("Map - "+title    );
	}
	/**
	 * Sets whether the base window can be resized or not
	 * @param status True enables resizing, false disables
	 */
	public void setResize(boolean status)
	{
		this.gridFrame.setResizable(status);
	}
	/**
	 * Sets the grid line color
	 * @param color The color you'd like to use
	 */
	public void setGridLinesColor(Color color)
	{
		this.gridPanel.setGroutingColor(color);
	}
	public void set3DGrid(boolean status)
	{
		this.gridPanel.setFlatSquares(status);
	}
	public void setTextFontColor(Color fontColor)
	{
		this.gridPanel.setTextFontColor(fontColor);
	}
	/**
	 * Checks whether specified coordinates are in the grid
	 * @param c Coordinates to check
	 * @return True if the coordinates exist in the grid, otherwise false
	 */
	public boolean isInMap(Coordinates c)
	{
		int rows=getRows()-1, cols=getColumns()-1;
		int x=c.getX(), y=c.getY();
		if(((y>=0)&&(y<=rows))&&((x>=0)&&(x<=cols)))
			return true;
		return false;
	}
	public void setColor(Coordinates c, Color col)
	{
		gridPanel.setColor(c, col);
	}
	public void setText(Coordinates c, String st)
	{
		gridPanel.setSquareText(c, st);
	}
	public void setSquare(Coordinates c, Color col, String st)
	{
		gridPanel.setSquare(c, col, st);
	}
	public Color getColor(Coordinates c)
	{
		return gridPanel.getColor(c);
	}
	/**
	 * Load a random schematics to random coordinates
	 * @param amount Number of schematics to load
	 */
	public void loadSchem(int amount)
	{
		for(int i=1; i<=amount; i++)
			loadSchem();
	}
	/**
	 * Load a random schematic to random coordinates
	 */
	public void loadSchem()
	{
		loadSchem(new Schematic(), new Coordinates(this));
	}
	/**
	 * Load a schematic to specified coordinates
	 * @param schemNum ID of schematic to load
	 * @param c Target coordinates
	 */
	public void loadSchem(int schemNum, Coordinates c)
	{
		loadSchem(new Schematic(schemNum), c);
	}
	/**
	 * Load a schematic to specified coordinates
	 * @param s Schematic to load
	 * @param loc Target coordinates
	 */
	public void loadSchem(Schematic s, Coordinates loc)
	{
		if(this.edges)
		{
			if(loc.getX()<3)
				loc=new Coordinates(3, loc.getY());
			if(loc.getX()>getColumns()-4)
				loc=new Coordinates(getColumns()-4, loc.getY());
			if(loc.getY()<3)
				loc=new Coordinates(loc.getX(), 3);
			if(loc.getY()>getRows()-4)
				loc=new Coordinates(loc.getX(), getRows()-4);
		}
		int sx=0, sy=0;
		int mapx=loc.getX()-2;
		int mapy=loc.getY()-2;
		for(int i=loc.getY()-2; i<loc.getY()+3; i++)
		{
			mapy=i;
			for(int j=loc.getX()-2; j<loc.getX()+3; j++)
			{
				mapx=j;
				if((isInMap(new Coordinates(mapx, mapy)))&&(this.pos[mapy][mapx]==null))
				{
					this.occupied[mapy][mapx]=false;
					Color temp=s.getColor(new Coordinates(sx, sy));
					if(temp==Color.black)
					{
						makeWall(new Coordinates(mapx, mapy));
					}
					if(this.coins[mapy][mapx]==null)
						gridPanel.setSquare(new Coordinates(mapx, mapy), temp, "");
				}
				sx++;
			}
			sx=0;
			sy++;
		}
	}
	private void makeWall(Coordinates c)
	{
		this.occupied[c.getY()][c.getX()]=true;
		if(this.coins[c.getY()][c.getX()]!=null)
			this.coins[c.getY()][c.getX()]=null;
		if(this.coins[c.getY()][c.getX()]==null)
			gridPanel.setSquare(new Coordinates(c.getX(), c.getY()), Color.black, "");
	}
	/**
	 * Render an entity at (0, 0)
	 * @param ent Entity to render
	 */
	public synchronized void render(Entity ent)
	{
		render(ent, new Coordinates(0, 0));
	}
	/**
	 * Render an entity at specified coordinates
	 * @param ent Entity to render
	 * @param x X value of render point
	 * @param y Y value of render point
	 */
	public synchronized void render(Entity ent, int x, int y)
	{
		render(ent, new Coordinates(x, y));
	}
	/**
	 * Delete an entity
	 * @param ent Entity to delete
	 */
	public synchronized void delete(Entity ent)
	{
		render(null, ent.getPos());
	}
	/**
	 * Render an entity at defined coordinates
	 * @param ent Entity to render
	 * @param pos Coordinates at which to render entity
	 */
	public synchronized void render(Entity ent, Coordinates pos)
	{
		if(isInMap(pos))
		{
			if(ent==null)
			{
				int x=pos.getX(), y=pos.getY();
				if(getEnt(pos)!=null)
				{
					getEnt(pos).setRendered(false);
					if(getEnt(pos).isPlayer())
						clearPlayer();
				}
				this.occupied[y][x]=false;
				this.pos[y][x]=null;
				gridPanel.setSquare(pos, this.DEFAULT_COLOR, "");
			}
			else
			{
				if((!ent.isRendered())&&(this.occupied[pos.y][pos.x]))
					pos=findClosestSmall(pos);
				if(!this.occupied[pos.y][pos.x])
				{
					if(ent.isRendered())
					{
						int x=ent.getPos().getX(), y=ent.getPos().getY();
						if((this.coins[y][x]!=null)&&(!ent.isPlayer()))
							gridPanel.setSquare(ent.getPos(), Coin.COLOR, ""+this.coins[y][x].getWorth());
						else
							gridPanel.setSquare(ent.getPos(), this.DEFAULT_COLOR, "");
						this.occupied[y][x]=false;
						this.pos[y][x]=null;
					}
					ent.setRendered(true);
					ent.setPos(pos);
					if((ent.isPlayer())&&(this.coins[pos.getY()][pos.getX()]!=null))
					{
						ent.addPoints(this.coins[pos.getY()][pos.getX()].getWorth());
						this.coins[pos.getY()][pos.getX()]=null;
					}
					this.occupied[pos.getY()][pos.getX()]=true;
					this.pos[pos.getY()][pos.getX()]=ent;
					if((this.player!=null)&&(ent.isPlayer())&&(!ent.equals(this.player)))
						ent.unPlayer();
					gridPanel.setSquare(pos, ent.getColor(), "");
				}
			}
		}
		try
		{
			Thread.sleep(50);
		}
		catch(Exception e)
		{
			System.out.println(e.getMessage());
		}
	}
	public synchronized void addCoin(Coin c)
	{
		Coordinates pos=c.getPos();
		if((!isOccupied(pos))&&(this.coins[pos.getY()][pos.getX()]==null))
		{
			this.coins[pos.y][pos.x]=c;
			setSquare(pos, Coin.COLOR, ""+this.coins[pos.getY()][pos.getX()].getWorth());
		}
	}
	public Entity getEnt(Coordinates c)
	{
		return this.pos[c.getY()][c.getX()];
	}
	public Entity getPlayer()
	{
		return this.player;
	}
	public void addPlayer()
	{
		for(int row=0; row<this.occupied.length; row++)
			for(int col=0; col<this.occupied[0].length; col++)
			{
				if((this.occupied[row][col])&&(this.pos[row][col]!=null))
				{
					Entity ent=this.pos[row][col];
					if(ent.isPlayer())
					{
						KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(this.d);
						this.player=ent;
					}
				}
			}
	}
	public void clearPlayer()
	{
		this.player.unPlayer();
		this.player=null;
		KeyboardFocusManager.getCurrentKeyboardFocusManager().removeKeyEventDispatcher(this.d);
	}
	public boolean hasPlayer()
	{
		if(this.player!=null)
			return true;
		return false;
	}
	/**
	 * Shows all occupied squares by coloring them cyan.
	 * 
	 * (DEBUG)
	 */
	public void revealOccupied()
	{
		for(int row=0; row<getRows(); row++)
			for(int col=0; col<getColumns(); col++)
			{
				Coordinates temp=new Coordinates(row, col);
				if(!isOccupied(temp))
					setSquare(temp, Color.cyan, "");
				delay(50);
			}
	}
	/**
	 * Checks if square at specified coordinates is occupied
	 * @param c Coordinates to check
	 * @return True if square is occupied by entity, else returns false
	 */
	public boolean isOccupied(Coordinates c)
	{
		return this.occupied[c.getY()][c.getX()];
	}
	public void handleKeyPress(int keyCode)
	{
		Coordinates c=this.player.getPos();
		int x=c.getX(), y=c.getY();
		switch (keyCode) 
		{
		case 37: //Left
			c=new Coordinates(x-1, y);
			break;
		case 38: //Up
			c=new Coordinates(x, y-1);
			break;
		case 39: //Right
			c=new Coordinates(x+1, y);
			break;
		case 40: //Down
			c=new Coordinates(x, y+1);
			break;
		case 27: //Escape
			System.exit(0);
			break;
		case 119: //W
			c=new Coordinates(x, y-1);
			break;
		case 97:  //A
			c=new Coordinates(x-1, y);
			break;
		case 83: //S
			c=new Coordinates(x, y+1);
			break;
		case 68: //D
			c=new Coordinates(x+1, y);
			break;
		case 76: //L
			loadSchem();
			break;
		case 67: //C
			loadSchem(14, this.player.getPos());
			break;
		case 65: //A
			break;
		case 61: //+
			this.player.setSpeed(this.player.getSpeed()-100);
			break;
		case 45: //-
			this.player.setSpeed(this.player.getSpeed()+100);
			break;
		}
		render(this.player, c);
		delay(1000);
		KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(this.d);
	}
	public Coordinates findClosestSmall(Coordinates c)
	{
		return findClosest(c, 5);
	}
	public Coordinates findClosest(Coordinates c, int radius)
	{
		System.out.println(c.toString()+", "+radius);
		int stop=0;
		radius=Math.abs(radius);
		if(radius==0)
		{
			radius=new Coordinates(0, 0).distance(new Coordinates(getColumns(), getRows()));
			stop=5;
		}
		Coordinates closest=c;
		for(int i=radius; i>=stop; i--)
		{
			for(int k=0; k<getRows(); k++)
				for(int l=0; l<getColumns(); l++)
				{
					Coordinates temp=new Coordinates(k, l);
					if((isInMap(temp))&&(temp.distance(c)==i)&&(!isOccupied(temp)))
						closest=temp;
				}
		}
		System.out.println(closest.toString());
		return closest;
	}
	/**
	 * Surrounds defined coordinates with a wall
	 * @param c Coordinates to surround
	 */
	public void surround(Coordinates c)
	{
		this.edges=false;
		loadSchem(14, c);
		this.edges=true;
	}
	/**
	 * Checks if specified coordinates are surrounded on all sides
	 * @param c Coordinates to check
	 * @return True if surrounded, else false
	 */
	public boolean surrounded(Coordinates c)
	{
		Coordinates[] sur={c.addDir(new Dir("up")), c.addDir(new Dir("down")), c.addDir(new Dir("left")), c.addDir(new Dir("right"))};
		for(int i=0; i<sur.length; i++)
			if(!isOccupied(sur[i]))
				return false;
		return true;
	}
	private boolean canMark(Coordinates c)
	{
		if(this.checked[c.getY()][c.getX()])
			return false;
		if(isOccupied(c))
			if(this.pos[c.getY()][c.getX()]==null)
				return false;
		return true;
	}
	/**
	 * A recursive method which will find all "blocked" squares and turn them into walls.
	 */
	public void check()
	{
		this.checked=new boolean[getRows()][getColumns()];
		for(int i=0; i<checked.length; i++)
			for(int j=0; j<checked[0].length; j++)
				this.checked[i][j]=false;
		check(new Coordinates());
		for(int i=0; i<checked.length; i++)
			for(int j=0; j<checked[0].length; j++)
				if(!this.checked[j][i])
					makeWall(new Coordinates(i, j));
				else
					this.checked[j][i]=false;
	}
	private void check(Coordinates c)
	{
		if(this.edges)
			if(canMark(c))
			{
				Coordinates[] sur={c.addDir(new Dir("up")), c.addDir(new Dir("down")), c.addDir(new Dir("left")), c.addDir(new Dir("right"))};
				this.checked[c.getY()][c.getX()]=true;
				for(int i=0; i<sur.length; i++)
					if(isInMap(sur[i]))
						check(sur[i]);
			}
	}
}